package com.nulldev.util.web.HttpServer3.extensions.nCaptcha.captchas.auto;

import com.nulldev.util.JVM.JVM;
import com.nulldev.util.VariableAPI.ArrayUtils;
import com.nulldev.util.languages.json.uniapi.UniJsonIterableValue;
import com.nulldev.util.languages.json.uniapi.UniJsonValue;
import com.nulldev.util.logging.Logger;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.manage.nullUtil;
import com.nulldev.util.security.hashAPI.Hashes;
import com.nulldev.util.security.hashAPI.Hashes.Hashables;
import com.nulldev.util.web.HttpServer3.Response;
import com.nulldev.util.web.HttpServer3.extensions.nCaptcha.backend.user.User;
import com.nulldev.util.web.HttpServer3.extensions.nCaptcha.backend.user.UserInfoType;
import com.nulldev.util.web.HttpServer3.extensions.nCaptcha.backend.util.UUIDCookieLoader;
import com.nulldev.util.web.HttpServer3.extensions.nCaptcha.captchas.auto.math.MathChallengeGenerator.MathChallengeContext;
import com.nulldev.util.web.HttpServer3.legacyPages.v1.RequestType;
import com.nulldev.util.web.HttpServer3.pages.v3.HttpPage3;

public class AutoPayloadSubmission extends HttpPage3 {

	private static final Logger log = LoggerFactory.getLogger();

	private static final String JSON_GENERIC_ERROR = UniJsonValue.object(1).add("error", true).toString();
	private static final String JSON_MISSING_COOKIE_ERROR = UniJsonValue.object(2).add("error", true).add("reason", "NCAPTCHA_MISSING_COOKIE").toString();

	private static final String JSON_MIME = "application/json; charset=utf-8";
	private static final double REQUIRED_PASSING_SCORE = 0.95;

	public AutoPayloadSubmission() {
		super("/captcha/auto_submit", true);
		super.setCachingMethod(CachingMethod.NO_CACHE);
	}

	@SuppressWarnings("resource")
	@Override
	public Response onRequest(final Request request) {
		if (!request.getType().equals(RequestType.POST)) {
			return new Response(405);
		}
		if (!request.hasInputStream()) {
			return new Response(411);
		}

		final User user = UUIDCookieLoader.userFromRequest(request);
		if (user == null) {
			return new Response(200, createStream(JSON_MISSING_COOKIE_ERROR)).setCustomMime(JSON_MIME);
		}

		final String body = toString(request.getInputStream());
		final String data = user.decryptPayload(body);

		if (data == null || !(data.startsWith("{") && data.endsWith("}"))) {
			return new Response(406, createStream(JSON_GENERIC_ERROR)).setCustomMime(JSON_MIME);
		}

		if (!request.hasHeader("X-NCAPTCHA-HASH")) {
			/* Return an actual JSON object, allows older browsers to fallback to v2. */
			return new Response(403, createStream(JSON_GENERIC_ERROR)).setCustomMime(JSON_MIME);
		}

		final String hash = request.header("X-NCAPTCHA-HASH");
		final String localHash = (String) Hashes.get(Hashables.SHA256).hash(data.getBytes());
		log.info("hash      =" + hash);
		log.info("local_hash=" + localHash);
		final UniJsonValue obj = UniJsonValue.parse(data);
		if (obj == null || obj.isNull()) {
			return new Response(503);
		}
		/* */
		double score = 0.6;
		/* */
		String prevUA = "";
		final UniJsonValue o = obj.get("userAgents").asArray();
		for (int i = 0; i < o.size(); i++) {
			final UniJsonValue s = o.get(i);
			if (prevUA.equals("")) {
				prevUA = s.toString();
				continue;
			}
			if (!prevUA.equals(s.toString())) {
				score -= 0.05;
			}
		}
		/* */
		final String ua = request.header("User-Agent");
		if (obj.getBoolean("headless") || obj.getBoolean("webdriver") || obj.getBoolean("framework") || hasBlacklistedUA(ua)) {
			score = 0;
		}
		/* */
		if (!obj.bool("invokedNaturally", false)) {
			score -= 0.6;
		}
		/* */
		if (obj.getBoolean("navigatorObjOk")) {
			score += 0.1;
		}
		/* */
		final String navHash = obj.getString("navigatorHash");
		final UniJsonIterableValue nav = obj.get("navigator").asArray();
		String localNavHash = "";
		for (final UniJsonValue uv : nav) {
			localNavHash += uv.getString("value");
			localNavHash += ",";
		}
		localNavHash = (String) Hashes.get(Hashables.SHA256).hash(localNavHash.getBytes());
		if (!localNavHash.equals(navHash))
			score -= 0.5;
		/* */
		if (obj.getBoolean("anti_adblock")) {
			score += 0.05;
		}
		/* */
		if (obj.hasValue("mathChallenge")) {
			final MathChallengeContext ctx = user.getMathChallengeContext();
			double result = obj.getDouble("mathChallenge", Double.MIN_VALUE);
			if (result == Double.MIN_VALUE)
				result = (float) obj.getFloat("mathChallenge");
			final double diff = Math.abs(result - ctx.getExpectedValue());
			if (nullUtil.IS_NON_MAINLINE_BUILD) {
				JVM.println("mathChallenge[result=" + result + ", expected=" + ctx.getExpectedValue() + ", diff=" + diff + "]");
				JVM.println(ctx.getPayload());
			}
			if (diff <= 0.98) {
				score += 0.2;
			} else {
				score -= 0.2;
			}
		} else {
			log.warn("Client is missing mathChallenge! Forcing to v2.");
			score = 0;
		}
		/* */
		if (obj.getBoolean("tor_canvas")) {
			score = 0;
		}
		/* */
		final long storage = obj.getLong("storage", 0L);
		if (storage == Integer.MAX_VALUE) {
			score += 0.2;
		} else if (storage == 1) {
			score += 0.1;
		} else if (storage == Integer.MIN_VALUE) {
			score -= 0.05;
		} else {
			log.warn("Unknown storage flag [" + storage + "], resetting challenge score.");
			score = 0;
		}
		/* */
		if (obj.hasValue("mimeTypes")) {
			final UniJsonValue mimeTypes = obj.get("mimeTypes");
			if (mimeTypes.isNull()) {
				score -= 0.2;
			} else {
				final boolean hasValidMimeTypes = obj.bool("mimeTypes");
				if (hasValidMimeTypes) {
					score += 0.1;
				} else {
					score -= 0.3;
				}
			}
		}
		/* */
		if (obj.hasValue("devTools")) {
			final UniJsonValue devTools = obj.get("devTools");
			if (devTools.isNull()) {
				score -= 0.2;
			} else {
				final boolean usingDevTools = obj.bool("devTools");
				if (!usingDevTools) {
					score += 0.1;
				} else {
					score -= 0.8;
				}
			}
		}
		/* */
		if (!obj.contains("safety_hash")) {
			score = 0;
		} else {
			final String safetyHash = obj.getString("safety_hash", null);
			if (!user.getUserInfoObject(UserInfoType.USER_INFO_V3_SAFETY_HASH).equals(safetyHash)) {
				score = 0;
			}
		}
		/* */
		final UniJsonValue result = UniJsonValue.object(2);
		result.add("passed", (score >= REQUIRED_PASSING_SCORE));
		result.add("score", score);
		if (score >= 0.95) {
			user.captchaPassed();
		} else {
			user.captchaFailed();
		}
		user.removeInfoObject(UserInfoType.USER_INFO_V3_SAFETY_HASH);
		return new Response(200, result, result).setCustomMime(JSON_MIME);
	}

	private static final String[] BLACKLISTED_USER_AGENTS = new String[]
		{ "HeadlessChrome", "headless", "Electron", "okhttp", "Python-urllib", "python-httpx", "python-requests", "aiohttp", "curl", "libwww-perl",
				"Go-http-client", "Phantom.js bot", "WinHTTP", "UnityWebRequest", "HTTPClient", "asynchttp", "node-fetch" };

	private static boolean hasBlacklistedUA(final String userAgent) {
		/*
		 * These User-Agents are very very likely automated, subject them to v2 by
		 * force.
		 */
		return ArrayUtils.StringArrayUtils.containsInString(BLACKLISTED_USER_AGENTS, userAgent);
	}

}
